Repository navigation
fix(ai,receipts): ai models --provider takes the LLM registry; run ids never repeat - #856
Merged
Merged
Conversation
The flag stored its value in args.provider, the slot of the global infrastructure --provider, so _validate_global_args read every LLM name as an infrastructure provider: `fluid ai models --provider gemini` exited 2 with "Unknown provider 'gemini' — installed providers: aws, databricks, gcp, local, snowflake". Its own choices were five hard-coded names. The flag now uses dest=llm_provider, as `ai setup` and `ai test` do, and LlmProviderChoices, the container `fluid forge --llm-provider` uses: the built-ins (listed in --help and in an invalid-choice error) plus installed fluid_build.llm_providers plugins, with no entry-point scan on --help. show_ai_models shows any of those names, and _provider_model_plan resolves mcp-sampling and plugins through get_llm_provider. The unfiltered `fluid ai models` output is unchanged. tests/test_cli_extension_commands.py asserted the value on args.provider, which pinned the bug; it now reads args.llm_provider.
The suffix was 32 random bits beside a millisecond timestamp, so ids made in one millisecond collided by the birthday bound. 1000 ids in a tight loop repeated one in roughly 1 run of 10,000, which made test_run_ids_are_unique fail once on CI with `assert 999 == 1000`. The suffix is now monotonic, as in a ULID or a UUIDv7 (RFC 9562, section 6.2): a random 31-bit start in each new millisecond, plus one for each later id in it. The last millisecond is kept when the clock steps back, a used-up suffix moves to the next millisecond, a lock guards the state, and os.register_at_fork resets it in a forked child. The format stays <11 hex digits>-<8 hex digits>, and nothing in forge parses it: the .fluid/agents run directories and fluid stats use _preview_panel.new_run_id. The new tests stop the clock and fix the random source, so a random-only suffix fails them every time. The flaky test itself is unchanged.
The defect scan of this branch reproduced two failures with FLUID_PROVIDER set. The global --provider defaults to it, so the module-scoped parser saw 'gcp' in args.provider, and with FLUID_PROVIDER=bogus main() stopped on the global check before ai models ran. Both tests now clear the variable. The fork test called _reset_run_id_state directly, so deleting the os.register_at_fork line would still have passed. It now forks for real in a fresh interpreter. With the hook stubbed out, the child repeats the parent's next id and the test fails.
The only conflict is CHANGELOG.md: both fix entries move into the new empty [Unreleased] above [0.29.0]. The ai models entry now says the bug was still in 0.29.0, which shipped without this fix.
ripwire --quality-delta gated on TestGenerateRunId growing from 12 to 95 lines (bar 60). Format and uniqueness stay in TestGenerateRunId. The clock, used-up suffix and fork cases move to TestRunIdSequence, and the fork test's child script becomes the module constant _FORK_SCRIPT.
fas89
added a commit
that referenced
this pull request
Nov 12, 2026
The only conflict was CHANGELOG.md, where both sides added [Unreleased] entries. Every entry is kept: this branch's Changed entry, then one Fixed list with #856's two entries and this branch's three.
9 of 15 tasks
fas89
added a commit
that referenced
this pull request
Nov 12, 2026
CHANGELOG [Unreleased] Fixed: keep both sides' entries, #856's two first, then this branch's three. fluid_build/cli/ai_setup.py merged cleanly.
This was referenced Nov 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Two small fixes found on 2026-11-12.
fluid ai models --providerworks. The flag stored its value inargs.provider, the slot ofthe global infrastructure
--provider._validate_global_argsthen read every LLM name as aninfrastructure provider:
fluid ai models --provider geminiexited 2 with "Unknown provider'gemini' — installed providers: aws, databricks, gcp, local, snowflake (see
fluid providers)".forge_docs has described this since 0.18.1.
dest="llm_provider", asai setupandai testalready do.LlmProviderChoices, the containerfluid forge --llm-provideruses: thebuilt-ins (listed in
--helpand in the invalid-choice error) plus installedfluid_build.llm_providersplugins.--helpscans no entry points.--provider claude-code,mcp-samplingor a plugin name prints that provider's model plan.The unfiltered
fluid ai modelsoutput is unchanged.Receipt run ids never repeat in one process.
generate_run_id(therun_idof.fluid/init-receipt.jsonandforge-receipt.json) paired a millisecond timestamp with 32 randombits. Ids made in one millisecond collided by the birthday bound, and
test_run_ids_are_uniquefailed once on CI with
assert 999 == 1000.millisecond starts it at a random 31-bit value, and each later id in that millisecond adds one.
millisecond.
os.register_at_forkresets it in a forked child.<11 hex digits>-<8 hex digits>), and nothing in forge parses it. The.fluid/agents/run directories andfluid statsuse_preview_panel.new_run_id.Commits: e19b3d1 (ai models), 7ee05c6 (run ids), e5bf382 and 1105c7b (test fixes from the PR gate), and a merge of main (the v0.29.0 cut, #855)
that moves both CHANGELOG entries into the new empty
[Unreleased].Design note
fluid ai models --provider <name>to see theprimary, routing and tier models forge would use, then
fluid forge --llm-provider <name>.Both commands take the same names.
ai models --provider:--jsonwith one key);claudestill shows asanthropic;mcp-samplingshows its placeholder plan, because the agent or the IDEpicks the model;
fluid forge --llm-providerdoes;appears for an LLM flag.
dest="llm_provider", as inai setupandai test. The choicescontainer is shared with
forge --llm-provider, so--helpstill scans no entry points.<11 hex>-<8 hex>, so receipt readers see no change.Uniqueness within one process is now guaranteed instead of likely, ids keep their time order
across clock steps, and a fork is safe.
Borrow before build
LlmProviderChoicesandget_llm_provider. I checked_preview_panel.new_run_id,_acquisition_common.generate_run_idandcopilot/store/audit_trail._next_audit_suffix. None fits: each has a different format ormeaning.
counter overflow may move the timestamp ahead.
uuid.uuid7(): a module-level last timestamp and counter.cpython#138862 shows the risk of
running ahead.
uuid.uuid7needs Python 3.14 (forge supports 3.10) and gives a 36-character id.Reviews (/ship gate)
depended on
$FLUID_PROVIDER. It is fixed in e5bf382, and a re-check auditor confirmed thefix.
after 1105c7b. Before that commit it gated on the run-id test class (95 lines, bar 60).
Type of Change
Documentation
docs/advanced/llm-providers.md(line 43) and the "fluid ai models --provider <name>fails in0.18.1" warning in
docs/walkthrough/ai-forge-data-model.mddescribe the exit 2 and ajqworkaround. Both need updating.
Checklist
pytest)ruff checkandblackwith no errorspython scripts/add_license_headers.py;examples/**exempt)Testing
assert 1 == 1000on main.main(["ai", "models", "--provider", "gemini", "--json"])returned 2 on main.FLUID_PROVIDERunset,gcpandbogus. The defect scan reproduced two env-dependentfailures, fixed in e5bf382.
os.register_at_forkstubbed out makes the child repeat the parent's next id.scripts/check_license_headers.pyandtests/test_license_headers.py;lint-imports(6 contracts kept) andscripts/mypy_strict.py;detect-secrets-hook1.5.0 over the changed files through xargs (rc 0; the baseline isunchanged).
forge receipts, agent_skills, acquisition_common).
build_parser()loads 1028 modules on main and on this branch, and none of mcp,httpx, jsonschema or litellm.
fluidCLI:fluid ai models --provider <name> --jsonfor all 10 registry names, plusGemini, exits 0with one key.
claudemaps toanthropic. No plan carriesendpointorapi_key.fluid ai models --jsonwithout a filter still lists anthropic, gemini, ollama and openai.fluid --provider aws ai models --provider gemini --jsonexits 0.fluid_build.llm_providersentry point (a*.dist-infoonPYTHONPATH) is acceptedand shown; without it the name is refused.
os.fork()child seeds its own suffix.fluid init demo --blank --yeswrites both receipts with well-formed ids.Known, not changed here:
fluid generate-pipeline --provider github_actionsexits 2 for the same reason(
cli/pipeline_generator.py:227).test_global_provider_flag.pyfails 2 tests whenFLUID_PROVIDERis set to an empty string,on main too.
Screenshots / Output